/*
 * Class
 * AbstractDistribution
 */
package pl.abstractvoid.distributions;

import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import javax.swing.ImageIcon;
import pl.abstractvoid.datamodel.parameters.ParameterSet;
import pl.abstractvoid.datamodel.parameters.SingleParameter;
import pl.abstractvoid.datamodel.parameters.exceptions.NoSuchParameterException;
import pl.abstractvoid.distributions.exceptions.ParameterValidationFailException;
import pl.abstractvoid.languages.LanguageProvider;
import pl.abstractvoid.rconnector.RCallerInstance;
import pl.abstractvoid.rconnector.RInterpreter;
import rcaller.exception.ParseException;

/**
 * This abstract class represents a probability distribution.
 * <p>
 * The abstraction involves methods for computing the distribution's output.
 * Moreover, it involves validation of the ParameterSet stored in the object.
 *
 * @author Wojciech Szałapski
 * @author Ivo Zieliński
 */
public abstract class AbstractDistribution implements Serializable {

    /**
     * Serial version UID for serialization and deserialization in different 
     * versions of the application.
     */
    static final long serialVersionUID =1L;
    /**
     * Set of all the parameters needed to compute the output for a
     * distribution.
     */
    private ParameterSet parameters;
    /**
     * Hash code of the parameter set.
     */
    private Double parametersHash;
    /**
     * R interpreter connected with the distribution's data.
     */
    protected RInterpreter rState;
    /**
     * Table of output results represented as SingleParameters.
     */
    protected SingleParameter[] outputTable;
    /**
     * Name of the property with the distribution's name.
     */
    protected String distributionName;
    /**
     * Image representing the plot of a cumulative distribution.
     */
    private ImageIcon cumulativeDistributionPlotImage;
    /**
     * Abstract file path of the image that stores the cumulative distribution
     * plot.
     */
    private File cumulativeDistributionPlotFile;
    /**
     * Image representing the plot of the probability density/mass function.
     */
    private ImageIcon probabilityPlotImage;
    /**
     * Abstract file path of the image that stores the probability density/mass 
     * function plot.
     */
    private File probabilityPlotFile;
    /**
     * R script performed to compute the output from the input set of
     * parameters.
     */
    private StringBuilder setUpOutputScript;
    /**
     * R script performed to generate the plot for cumulative distribution.
     */
    private StringBuilder setUpCumulativeDistributionPlotScript;
    /**
     * R script performed to generate the plot for probability density/mass 
     * function.
     */
    private StringBuilder setUpProbabilityPlotScript;

    /**
     * Constructor initializing the script for setting up the output and the
     * names in the output table.
     *
     * @param setUpOutputScript Script executed to compute the output for the
     *                          distribution.
     * @param setUpCumulativeDistributionPlotScript Script executed to draw a
     *                                              plot for cumulative
     *                                              distribution.
     * @param setUpProbabilityPlotScript Script executed to draw a plot for 
     *                                   the probability density/mass function.
     */
    protected AbstractDistribution(StringBuilder setUpOutputScript,
            StringBuilder setUpCumulativeDistributionPlotScript,
            StringBuilder setUpProbabilityPlotScript) {
        this.setUpOutputScript = setUpOutputScript;
        this.setUpCumulativeDistributionPlotScript = setUpCumulativeDistributionPlotScript;
        this.setUpProbabilityPlotScript = setUpProbabilityPlotScript;
        initializeOutputTable();
    }

    /**
     * Returns the table of output values.
     *
     * @return Table of output values.
     */
    public SingleParameter[] getOutputTable() {
        return outputTable;
    }

    /**
     * Returns set of all distribution's parameters.
     *
     * @return Set of all the input parameters of the distribution.
     */
    public ParameterSet getParameters() {
        return parameters;
    }

    /**
     * Returns the instance of R interpreter connected with this distribution's
     * data.
     *
     * @return R interpreter for this instance of probability distribution.
     */
    public RInterpreter getrState() {
        return rState;
    }

    /**
     * Returns image of the cumulative distribution's plot.
     *
     * @return ImageIcon representing cumulative distribution's plot.
     */
    public ImageIcon getCumulativeDistributionPlotImage() {
        return cumulativeDistributionPlotImage;
    }
    
    /**
     * Returns image of the probability density/mass function plot.
     *
     * @return ImageIcon representing probability density/mass function plot.
     */
    public ImageIcon getProbabilityPlotImage() {
        return probabilityPlotImage;
    }

    /**
     * Gets up-to-date value of the SingleParameter given by it's name.
     *
     * @param parameterName Name of a single parameter.
     * @return SingleParameter's value got from RCallerInstance.
     * @throws NoSuchParameterException
     * @throws ParseException
     */
    public double getSingleParameterValue(String parameterName)
            throws NoSuchParameterException, ParseException {
        return rState.getSingleParameterValue(parameterName);
    }

    /**
     * Gets up-to-date values of the vector of parameters of the chosen name.
     *
     * @param parameterName Name of the vector of parameters inside a table.
     * @return ArrayList of consecutive values from the chosen vector.
     * @throws NoSuchParameterException
     */
    public ArrayList<Double> getParameterVectorValue(String parameterName)
            throws NoSuchParameterException {
        return rState.getParameterVectorValue(parameterName);
    }

    /**
     * Updates set of the parameters and validates it's correctness.
     *
     * @param parameters Set of new parameters.
     * @throws NoSuchParameterException
     */
    public void updateInput(ParameterSet parameters)
            throws NoSuchParameterException {
        this.parameters = parameters;
    }

    /**
     * Initializes output values and the plot.
     * 
     * @throws NoSuchParameterException
     * @throws ParameterValidationFailException
     * @throws IOException
     */
    public void updateOutput()
            throws NoSuchParameterException, ParameterValidationFailException, 
                   IOException {
        validate(parameters);
        double parametersHashCode = parameters.getParametersHash();
        if (parametersHash == null || parametersHashCode != parametersHash.doubleValue()) {
            parametersHash = new Double(parametersHashCode);
            setUpOutput();
            loadOutput();
            setUpPlots();
        }
    }

    /**
     * Sets up output values using R interpreter.
     */
    private void setUpOutput() {
        rState.runCustomCode(setUpOutputScript.toString(), RInterpreter.outputRList);
    }

    /**
     * Gets the output values for the distribution from R interpreter once they
     * are computed.
     *
     * @throws NoSuchParameterException
     */
    private void loadOutput() throws NoSuchParameterException {
        for (SingleParameter parameter : outputTable) {
            parameter.setParameterValue(rState.getSingleParameterValue(parameter.getParameterName()));
        }
    }

    /**
     * Validates the parameters set.
     * <p>
     * No exception is thrown if validation succeed.
     *
     * @param parameters Set of the parameters to validate.
     * @throws ParameterValidationFailException
     */
    protected abstract void validate(ParameterSet parameters)
            throws ParameterValidationFailException;

    /**
     * Gets the parameter set from the instance of R interpreter.
     *
     * @return Up-to-date set of input parameters for this distribution.
     * @throws NoSuchParameterException
     * @throws ParseException
     */
    public abstract ParameterSet loadParameters()
            throws NoSuchParameterException, ParseException;

    /**
     * Initializes the table of output values with visible names and unique
     * variable names used by R interpreter.
     */
    protected abstract void initializeOutputTable();

    /**
     * Sets up the cumulative distribution and probability density/mass function 
     * plots by performing suitable R scripts.
     */
    private void setUpPlots() throws IOException {
        cumulativeDistributionPlotFile = rState.startPlot();
        rState.runCustomCode(setUpCumulativeDistributionPlotScript.toString());
        rState.endPlot();
        cumulativeDistributionPlotImage = rState.getPlot(cumulativeDistributionPlotFile);
        probabilityPlotFile = rState.startPlot();
        rState.runCustomCode(setUpProbabilityPlotScript.toString());
        rState.endPlot();
        probabilityPlotImage = rState.getPlot(probabilityPlotFile);
    }

    /**
     * Reads a distribution object from the stream and initializes it properly.
     * 
     * @param o Input stream from the distribution is deserialized.
     * @throws IOException
     * @throws ClassNotFoundException
     * @throws NoSuchParameterException 
     */
    private void readObject(ObjectInputStream o)
            throws IOException, ClassNotFoundException, NoSuchParameterException {
        distributionName = (String) o.readObject();
        parameters = (ParameterSet) o.readObject();
        setUpCumulativeDistributionPlotScript = (StringBuilder) o.readObject();
        setUpProbabilityPlotScript = (StringBuilder) o.readObject();
        setUpOutputScript = (StringBuilder) o.readObject();
        rState = new RCallerInstance();
        initializeOutputTable();
        updateInput(parameters);
    }
    
    /**
     * Writes a distribution object into the stream.
     * 
     * @param s Output stream for serializing the object.
     * @throws IOException 
     */
    private void writeObject(ObjectOutputStream s) throws IOException {
        s.writeObject(distributionName);
        s.writeObject(parameters);
        s.writeObject(setUpCumulativeDistributionPlotScript);
        s.writeObject(setUpProbabilityPlotScript);
        s.writeObject(setUpOutputScript);
    }
    
    /**
     * Cleans up when the distribution is deleted.
     */
    public void cleanUp() {
        rState.cleanUp();
    }
    
    /**
     * Reloads language after it's change.
     */
    public void reloadLanguage() {
        rState.loadCustomFunctions();
    }
    
    /**
     * Returns the name of the distribution in the current language.
     * 
     * @return Displayed name of the distribution.
     */
    public String getDistributionName() {
        return LanguageProvider.getInstance().getDistributionString(distributionName);
    }
}